/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: crypt.c,v 1.7 2005/07/30 13:58:30 pullmoll Exp $
 *****************************************************************************/
#include "crypt.h"
#if	CRYPT_TEST
#define	_mem_alloc_h_
#define	smalloc(size) malloc(size)
#define	scalloc(num,size) calloc(num,size)
#define	sfree(ptr) if (NULL != ptr) { free(ptr); ptr = NULL; }
#define osd_usleep(x) usleep(x)
#else
#include "shmalloc.h"
#endif
#include "memalloc.h"
#include "tools.h"
#include "logger.h"

/* number of crypto modules */
enum {
	MODULE_CRYPT0	= 0,
	MODULE_TWOFISH,
	MODULES
};

static const char *module_name[] = {
	"crypt0",
	"twofish",
	NULL
};

#undef	KEYSIZE
#define	KEYSIZE	65536

typedef struct crypt_s {
	osd_sem_t sem;
	int64_t xold;
	int64_t yold;
	int64_t zold;
	size_t offs;
	uint8_t which;
	uint8_t key[KEYSIZE];
}	crypt_t;

crypt_t *g_crypt = NULL;

#define	COUNTBIT(n,m) { \
	size_t bit = (n >> m) & 1; \
	if (bit == last) { \
		if (0 == last) \
			run0++; \
		else \
			run1++; \
	} else if (0 == last) { \
		if (run0 > max0) \
			max0 = run0; \
		runs[(run0 >= 32) ? 31 : run0] += 1; \
		run0 = 0; \
		last = 1; \
	} else { \
		if (run1 > max1) \
			max1 = run1; \
		runs[(run1 >= 32) ? 31 : run1] += 1; \
		run1 = 0; \
		last = 0; \
	} \
	bits[m] += bit; \
}

int crypto_diequick(const char *name, void *buffer, size_t size)
{
	uint32_t *u32 = (uint32_t *)buffer;
	uint32_t last;
	size_t total;
	size_t ones;		/* overall one bits */
	size_t bits[32];	/* one bits per column */
	size_t runs[32];	/* bit runs frequencies */
	size_t run0;		/* current 0 bits run length */
	size_t run1;		/* current 1 bits run length */
	size_t max0;		/* maximum 0 bits run length */
	size_t max1;		/* maximum 1 bits run length */
	size_t offs;
	size_t n;
	size_t exp;			/* expected 1 bits per column */
	size_t avg;
	size_t dev;
	size_t min, max;
	FUN("crypto_diequick");

#if	DEBUG == 0
	(void)name;
#endif
	memset(bits, 0, sizeof(bits));
	memset(runs, 0, sizeof(runs));
	total = size * 8;
	ones = 0;
	max0 = max1 = 0;
	run0 = run1 = 0;
	last = 0;
	for (offs = 0; offs < size/4; offs++) {
		register uint32_t n = u32[offs];
		/* count occurences for each bit slot and 1/0 runs */
		COUNTBIT(n, 0); COUNTBIT(n, 1); COUNTBIT(n, 2); COUNTBIT(n, 3);
		COUNTBIT(n, 4); COUNTBIT(n, 5); COUNTBIT(n, 6); COUNTBIT(n, 7);
		COUNTBIT(n, 8); COUNTBIT(n, 9); COUNTBIT(n,10); COUNTBIT(n,11);
		COUNTBIT(n,12); COUNTBIT(n,13); COUNTBIT(n,14); COUNTBIT(n,15);
		COUNTBIT(n,16); COUNTBIT(n,17); COUNTBIT(n,18); COUNTBIT(n,19);
		COUNTBIT(n,20); COUNTBIT(n,21); COUNTBIT(n,22); COUNTBIT(n,23);
		COUNTBIT(n,24); COUNTBIT(n,25); COUNTBIT(n,26); COUNTBIT(n,27);
		COUNTBIT(n,28); COUNTBIT(n,29); COUNTBIT(n,30); COUNTBIT(n,31);
		/* add number of 1 bits */
		n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
		n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
		n = (n + (n >> 4)) & 0x0f0f0f0f;
		n = n + (n >> 8);
		n = n + (n >> 16);
		ones += n & 0xff;
	}

	/* average and deviation from expected bit counts */
	avg = 0;
	dev = 0;
	exp = size * 8 / 32 / 2;
	for (n = 0; n < 32; n++) {
		avg += bits[n];
		dev += (bits[n] < exp) ? exp - bits[n] : bits[n] - exp;
	}
	avg /= 32;
	dev /= 32;
	LOGS(L_CRYPTO,L_MINOR,("'%s' diequick tests\n" \
		"one bits: %7u of %7u\n" \
		"one bits in bit columns:\n" \
		"bit 0- 7: %+7d %+7d %+7d %+7d %+7d %+7d %+7d %+7d\n" \
		"bit 8-15: %+7d %+7d %+7d %+7d %+7d %+7d %+7d %+7d\n" \
		"bit16-23: %+7d %+7d %+7d %+7d %+7d %+7d %+7d %+7d\n" \
		"bit24-31: %+7d %+7d %+7d %+7d %+7d %+7d %+7d %+7d\n" \
		"average:%+d, deviation:%u\n" \
		"run length frequencies (both 0 and 1 bit runs):\n" \
		" 1 to  8: %7u %7u %7u %7u %7u %7u %7u %7u\n" \
		" 9 to 16: %7u %7u %7u %7u %7u %7u %7u %7u\n" \
		"17 to 24: %7u %7u %7u %7u %7u %7u %7u %7u\n" \
		"25 to 32: %7u %7u %7u %7u %7u %7u %7u %7u (++)\n" \
		"max run length of 0-bits:%u, 1-bits:%u\n",
		name, (unsigned)ones, (unsigned)size * 8,
		(int)(bits[ 0] - exp), (int)(bits[ 1] - exp),
		(int)(bits[ 2] - exp), (int)(bits[ 3] - exp),
		(int)(bits[ 4] - exp), (int)(bits[ 5] - exp),
		(int)(bits[ 6] - exp), (int)(bits[ 7] - exp),
		(int)(bits[ 8] - exp), (int)(bits[ 9] - exp),
		(int)(bits[10] - exp), (int)(bits[11] - exp),
		(int)(bits[12] - exp), (int)(bits[13] - exp),
		(int)(bits[14] - exp), (int)(bits[15] - exp),
		(int)(bits[16] - exp), (int)(bits[17] - exp),
		(int)(bits[18] - exp), (int)(bits[19] - exp),
		(int)(bits[20] - exp), (int)(bits[21] - exp),
		(int)(bits[22] - exp), (int)(bits[23] - exp),
		(int)(bits[24] - exp), (int)(bits[25] - exp),
		(int)(bits[26] - exp), (int)(bits[27] - exp),
		(int)(bits[28] - exp), (int)(bits[29] - exp),
		(int)(bits[30] - exp), (int)(bits[31] - exp),
		(int)(avg - exp), (unsigned)dev,
		(unsigned)runs[ 0], (unsigned)runs[ 1],
		(unsigned)runs[ 2], (unsigned)runs[ 3],
		(unsigned)runs[ 4], (unsigned)runs[ 5],
		(unsigned)runs[ 6], (unsigned)runs[ 7],
		(unsigned)runs[ 8], (unsigned)runs[ 9],
		(unsigned)runs[10], (unsigned)runs[11],
		(unsigned)runs[12], (unsigned)runs[13],
		(unsigned)runs[14], (unsigned)runs[15],
		(unsigned)runs[16], (unsigned)runs[17],
		(unsigned)runs[18], (unsigned)runs[19],
		(unsigned)runs[20], (unsigned)runs[21],
		(unsigned)runs[22], (unsigned)runs[23],
		(unsigned)runs[24], (unsigned)runs[25],
		(unsigned)runs[26], (unsigned)runs[27],
		(unsigned)runs[28], (unsigned)runs[29],
		(unsigned)runs[30], (unsigned)runs[31],
		(unsigned)max0, (unsigned)max1));

	/* check that the number of one bits is in range: expect +/- expect/16 */
	min = total/2 - total/2 / 16;
	max = total/2 + total/2 / 16;
	if (ones < min || ones > max) {
		LOGS(L_CRYPTO,L_ERROR,("'%s' fails ones range test\n", name));
		errno = EINVAL;
		return -1;
	}

	/* check that each bit column's 1bits count is in range: exp +/- exp/16 */
	min = exp - exp / 16;
	max = exp + exp / 16;
	if (avg < min || avg > max) {
		LOGS(L_CRYPTO,L_ERROR,("'%s' fails avg range test\n", name));
		errno = EINVAL;
		return -1;
	}

	/* check that no bit column is all zeroes or all ones */
	total /= 32;
	for (n = 0; n < 32; n++) {
		/* any column with all 0 or 1 bits? */
		if (0 == bits[n] || total == bits[n]) {
			LOGS(L_CRYPTO,L_ERROR,("'%s' fails bit[%d] test\n",
				name, (int)n));
			errno = EINVAL;
			return -1;
		}
	}

	/* the data passed the primitive tests... */
	return 0;
}

/*
 * Lorenz' non linear equations (aka 'butterfly function'):
 *
 * dx/dt = sigma * y - sigma * x
 * dy/dt = rho * x - y - x * z
 * dz/dt = x * y - gamma * z
 *
 * ---
 * Parameters according to
 * Ian Stewart, "Spielt Gott Roulette", (Insel TB, Frankfurt 1993)
 * sigma = 10.0
 * rho = 28.0
 * gamma = 8.0/3.0
 */

#define	FXBITS	32
#define	FXMULT	((int64_t)1<<FXBITS)
#define	FXMASK	(uint64_t)(FXMULT-1)

/* sigma = 10.0 */
#define	SIGMA	((int64_t)10 << FXBITS)

/* rho = 28.0 */
#define	RHO		((int64_t)28 << FXBITS)

/* gamma = 8.0/3.0 = 2.66666666...; 32 bits fraction is 0x55555555 */
#define	GAMMA	(((int64_t)2 << FXBITS) | 0x55555555)

/* step width = 1/65536 */
#define	STEPSHIFT	16

/*
 *	fxmul(a,b)
 *	Fixed point multiplication of two values with 32.32 bits
 *	The product is taken from the middle 64 of the resulting
 *	128 bits, cutting off the fraction*fraction part in the
 *	lower 32 bits as well as the integer*integer overflow in
 *	the uppermost 32 bits (which should always be zero)
 */
static inline int64_t fxmul(int64_t a, int64_t b)
{
	uint32_t al, ah, bl, bh;
	uint64_t t1, t2, t3;
	int64_t result;
	int sign = 0;

	if (a < 0) {
		sign ^= 1;
		a = -a;
	}
	al = (uint32_t)a;
	ah = (uint32_t)(a >> 32);

	if (b < 0) {
		sign ^= 1;
		b = -b;
	}
	bl = (uint32_t)b;
	bh = (uint32_t)(b >> 32);

	/* fraction * fraction */
	t1 = al * bl;
	/* keep the upper 32 bits */
	t1 = t1 >> 32;
	/* fraction1 * integer2 */
	t2 = (uint64_t)al * (uint64_t)bh;
	/* fraction2 * integer1 */
	t3 = (uint64_t)ah * (uint64_t)bl;
	/* lower 32 bits of the result */
	result = (uint32_t)(t1 + t2 + t3);
	/* keep the upper 32 bits of t2 and t3 */
	t1 = (t2 >> 32) + (t3 >> 32);
	/* integer1 * integer2 */
	t2 = (uint64_t)ah * (uint64_t)bh;
	/* upper 32 bits of the result */
	result |= (int64_t)((t1 + t2) << 32);

	/* adjust sign */	
	if (0 != sign)
		result = -result;
	return result;
}

#if	CRYPT_TEST_PRINT
static char fxsign(int64_t x)
{
	return (x < 0) ? '-' : '+';
}

static unsigned fxint(int64_t x)
{
	return (unsigned)((x < 0) ? (-x >> FXBITS) : (x >> FXBITS));
}

static unsigned fxfract(uint64_t mult, int64_t x)
{
	if (x < 0)
		x = -x;
	return (unsigned)(mult * (x & FXMASK) / FXMULT);
}
#endif

static uint8_t rnd_next(void)
{	
	crypt_t *c = g_crypt;
	int64_t x = c->xold;
	int64_t y = c->yold;
	int64_t z = c->zold;
	int64_t dx, dy, dz;
	uint8_t res, px, py, pz, ox, oy, oz;

	dx = fxmul(SIGMA, y - x);
	dy = fxmul(RHO, x) - y - fxmul(x, z);
	dz = fxmul(x, y) - fxmul(GAMMA, z);

	c->xold = x + (dx >> STEPSHIFT);
	c->yold = y + (dy >> STEPSHIFT);
	c->zold = z + (dz >> STEPSHIFT);

	/*
	 * These bits of the 64 bit fixed point dx,dy,dz are used in the PRNG:
	 * iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii.ffffffffffffffffffffffffffffffff
	 * px/py/pz ---------------------------------------------^^^^^^^^---
	 * ox/oy/oz ------------------------------------^^^^^^^^------------
	 */

	dx >>= 3;
	px = (uint8_t)dx;
	dx >>= 9;
	ox = (uint8_t)dx;

	dy >>= 3;
	py = (uint8_t)dy;
	dy >>= 9;
	oy = (uint8_t)dy;

	dz >>= 3;
	pz = (uint8_t)dz;
	dz >>= 9;
	oz = (uint8_t)dz;

	/* add ox, oy and oz to the offset */
	c->offs = (c->offs + ox + oy + oz) % KEYSIZE;

	/* next PRNG value is (key[offs] xor px)+ py, replace with pz */
	res = (c->key[c->offs] ^ px) + py;
	c->key[c->offs] = pz;

#if	CRYPT_TEST_PRINT
	printf("%c%u.%09u %c%u.%09u %c%u.%09u px:%02x py:%02x pz:%02x offs:%04x:%02x\n",
		fxsign(x), fxint(x), fxfract(1000000000,x),
		fxsign(y), fxint(y), fxfract(1000000000,y),
		fxsign(z), fxint(z), fxfract(1000000000,z),
		px, py, pz, c->offs, res);
#endif
	return res;
}

/***********************************************************************
 *	rnd_get()
 *	Get 'size' bytes of random data to the specified buffer
 ***********************************************************************/
#define	RNSIZE	4096
int rnd_get(void *buffer, size_t size)
{
	uint8_t *pb = (uint8_t *)buffer;
	size_t i;
	int rc = 0;
	FUN("rnd_get");

	if (NULL == g_crypt) {
		LOGS(L_CRYPTO,L_ERROR,("g_crypt not initialized\n"));
		die(1, "g_crypt() is not initialized\n");
	}

	if (0 != (rc = osd_sem_wait(&g_crypt->sem))) {
		const char *errmsg = strerror(errno);
		LOGS(L_CRYPTO,L_ERROR,("locking crypt failed (%s)\n",
			errmsg));
		die(1, "locking crypt failed (%s)", errmsg);
	}
	for (i = 0; i < size; i++) {
		pb[i] = rnd_next();
	}
	osd_sem_post(&g_crypt->sem);

	return 0;
}

/***********************************************************************
 *	rnd_entropy()
 *	Add some entropy to the PRNG.
 ***********************************************************************/
void rnd_entropy(int value)
{
	crypt_t *c = g_crypt;
	FUN("rnd_entropy");

	if (NULL == g_crypt) {
		LOGS(L_CRYPTO,L_ERROR,("g_crypt not initialized\n"));
		die(1, "g_crypt() is not initialized\n");
	}

	if (value < 0) {
		c->which = (c->which + (value & 7)) % 4;
		value = -value / 8;
	}
	switch (c->which) {
	case 0:
		c->xold = (c->xold < 0) ? c->xold + value : c->xold - value;
		c->which = 1;
		break;
	case 1:
		c->yold = (c->yold < 0) ? c->yold + value : c->yold - value;
		c->which = 2;
		break;
	case 2:
		c->zold = (c->zold < 0) ? c->zold + value : c->zold - value;
		c->which = 3;
		break;
	case 3:
		c->offs = (c->offs + value) % KEYSIZE;
		c->which = 0;
		break;
	}
}

/* --------------------------------------------------------------------- */

int encrypt_msg(size_t module, void *ptr, void *ct, void *pt,
	size_t *outsize, size_t insize)
{
	int rc;
	FUN("encrypt_msg");

	if (NULL == ptr) {
		LOGS(L_CRYPTO,L_DEBUG,("ptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == ct) {
		LOGS(L_CRYPTO,L_DEBUG,("ct is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == pt) {
		LOGS(L_CRYPTO,L_DEBUG,("pt is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == outsize) {
		LOGS(L_CRYPTO,L_DEBUG,("outsize is NULL\n"));
		errno = EINVAL;
		return -1;
	}

	switch (module) {
	case MODULE_CRYPT0:
		rc = crypt0_encrypt_msg(ptr, ct, pt, outsize, insize);
		break;

	case MODULE_TWOFISH:
		rc = crypt7_encrypt_msg(ptr, ct, pt, outsize, insize);
		break;

	default:
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}

	if (0 != rc) {
		LOGS(L_CRYPTO,L_ERROR,("%s_encrypt(%p,%p,%p,%#x,%#x) failed (%s)\n",
			module_name[module],
			ptr, ct, pt,
			(unsigned)*outsize, (unsigned)insize,
			strerror(errno)));
	}

	return rc;
}

int decrypt_msg(size_t module, void *ptr, void *pt, void *ct,
	size_t *outsize, size_t insize)
{
	int rc;
	FUN("decrypt_msg");

	if (NULL == ptr) {
		LOGS(L_CRYPTO,L_DEBUG,("ptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == pt) {
		LOGS(L_CRYPTO,L_DEBUG,("pt is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == ct) {
		LOGS(L_CRYPTO,L_DEBUG,("ct is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (NULL == outsize) {
		LOGS(L_CRYPTO,L_DEBUG,("outsize is NULL\n"));
		errno = EINVAL;
		return -1;
	}

	switch (module) {
	case MODULE_CRYPT0:
		rc = crypt0_decrypt_msg(ptr, pt, ct, outsize, insize);
		break;

	case MODULE_TWOFISH:
		rc = crypt7_decrypt_msg(ptr, pt, ct, outsize, insize);
		break;

	default:
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}

	if (0 != rc) {
		LOGS(L_CRYPTO,L_ERROR,("%s_decrypt(%p,%p,%p,%#x,%#x) failed (%s)\n",
			module_name[module],
			ptr, pt, ct,
			(unsigned)*outsize, (unsigned)insize,
			strerror(errno)));
	}

	return rc;
}

static int crypto_modules_diequick(size_t module, void *ptr)
{
	uint8_t *buffer, *p;
	size_t insize;
	size_t outsize;
	size_t ioffs, isize, osize;
	int rc = 0;
	FUN("crypto_modules_diequick");

 	insize = 100000 * sizeof(uint32_t);
 	outsize = insize * 2;
	LOGS(L_CRYPTO,L_DEBUG,("insize: %u (%#x) outsize: %u (%#x)\n",
		(unsigned)insize, (unsigned)insize,
		(unsigned)outsize, (unsigned)outsize));
	buffer = (uint8_t *)xcalloc(outsize, sizeof(uint8_t));
	for (ioffs = 0, p = buffer; ioffs < insize; ioffs += 4096) {
		isize = (ioffs + 4096) >= insize ? insize - ioffs : 4096;
		osize = isize * 2;
		switch (module) {
		case MODULE_CRYPT0:
			/* crypt0 does not do anything */
			break;
		case MODULE_TWOFISH:
			rc = crypt7_encrypt_msg(ptr, p, p, &osize, isize);
			break;
		}
		if (0 != rc)
			break;
		p += osize;
	}

	if (0 == rc) {
		outsize = (size_t)(p - buffer);
		rc = crypto_diequick(module_name[module], buffer, outsize);
	}

	xfree(buffer);
	return rc;
}

int crypto_init(size_t module, void **pptr, void *data, size_t size,
	int diequick)
{
	int init = 0, rc;
	FUN("crypto_init");

	if (NULL == pptr) {
		LOGS(L_CRYPTO,L_DEBUG,("pptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}
	if (-1 == crypto_module_supp(module)) {
		LOGS(L_CRYPTO,L_ERROR,("unsupported module ID %d\n",
			(int)module));
#ifdef	ENOTSUP
		errno = ENOTSUP;
#else
#ifdef	ENOTSUPP
		errno = ENOTSUPP;
#else
		errno = EINVAL;
#endif
#endif
		return -1;
	}

	switch (module) {
	case MODULE_CRYPT0:
		rc = crypt0_init(pptr, init, data, size);
		break;

	case MODULE_TWOFISH:
		rc = crypt7_init(pptr, init, data, size);
		break;

	default:
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}

	if (0 != rc) {
		LOGS(L_CRYPTO,L_ERROR,("%s_crypto_init(%p,%d,%p,%#x) failed (%s)\n",
			module_name[module],
			pptr, (int)init, data, (unsigned)size,
			strerror(errno)));
		return rc;
	}

	/* shall we run the 'diequick' tests now? */
	if (0 != diequick) {
		rc = crypto_modules_diequick(module, *pptr);
	}

	return rc;
}

int crypto_exit(size_t module, void *ptr)
{
	int rc;
	FUN("crypto_exit");

	if (module >= MODULES) {
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}
	if (NULL == ptr) {
		LOGS(L_CRYPTO,L_DEBUG,("ptr is NULL\n"));
		errno = EINVAL;
		return -1;
	}

	switch (module) {
	case MODULE_CRYPT0:
		rc = crypt0_exit(ptr);
		break;

	case MODULE_TWOFISH:
		rc = crypt7_exit(ptr);
		break;

	default:
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}

	if (0 != rc) {
		LOGS(L_CRYPTO,L_ERROR,("%s_crypto_exit(%p) failed (%s)\n",
			module_name[module], ptr, strerror(errno)));
	}

	return rc;
}

const char *crypto_module_name(size_t module)
{
	FUN("crypto_module_name");

	if (module >= MODULES) {
		LOGS(L_CRYPTO,L_DEBUG,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return NULL;
	}

	return module_name[module];
}

size_t crypto_module_indx(const char *module)
{
	size_t i;
	FUN("crypto_module_indx");

	for (i = 0; i < MODULES; i++)
		if (0 == strcmp(module, module_name[i]))
			return i;

	LOGS(L_CRYPTO,L_ERROR,("module '%s' not found\n",
		module));
	errno = EINVAL;
	return 0;
}

int crypto_module_supp(size_t module)
{
	FUN("crypto_module_supp");

	if (module >= MODULES) {
		LOGS(L_CRYPTO,L_ERROR,("invalid module ID %d\n",
			(int)module));
		errno = EINVAL;
		return -1;
	}

	/* If this module is not supported, return -1 */
	if (0 != g_conf->crypto_reject[module]) {
		LOGS(L_CRYPTO,L_DEBUG,("module ID %d is not supported\n",
			(int)module));
#ifdef	ENOTSUP
		errno = ENOTSUP;
#else
#ifdef	ENOTSUPP
		errno = ENOTSUPP;
#else
		errno = EINVAL;
#endif
#endif
		return -1;
	}

	return 0;
}

/* Safe read with retry to fetch initial entropy from /dev/urandom */
static size_t read_urandom(int fd, void *buff, size_t size)
{
	uint8_t *pb = (uint8_t *)buff;
	size_t done;
	size_t delay;
	int rc;
	FUN("read_urandom");

	for (done = 0, delay = 50; done < size; done++) {
		if (1 == (rc = read(fd, &pb[done], 1)))
			continue;
		/* we have an error condition... */
		/* see if it's EAGAIN and if we should retry */
		if (EAGAIN == errno) {
			delay *= 2;
			/* at most 100 milliseconds between reads */
			if (delay > 100000)
				delay = 100000;
			LOGS(L_CRYPTO,L_ERROR,("EAGAIN while reading /dev/urandom; wait %s\n",
				usec_str(delay)));
			osd_usleep(delay);
			done--;
			continue;
		}
		LOGS(L_CRYPTO,L_ERROR,("Error reading /dev/urandom %d (%s)\n",
			rc, strerror(errno)));
		return done;
	}

	return done;
}

int crypto(void)
{
	uint8_t *buffer;
	size_t size;
	int fd = -1;
	int rc = 0;
	FUN("crypto");

	size = sizeof(crypt_t);
	g_crypt = (crypt_t *)scalloc(size, 1);

	if (0 != osd_sem_init(&g_crypt->sem, 1, 1)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL: osd_sem_init(%p,%d,%d) call failed (%s)\n",
			&g_crypt->sem, 1, 1, strerror(errno)));
		return -1;
	}

	fd = open("/dev/urandom", O_RDONLY);
	if (-1 == fd) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL: cannot open('%s', O_RDONLY) (%s)\n",
			"/dev/urandom", strerror(errno)));
		errno = EIO;
		return -1;
	}
	size = sizeof(g_crypt->xold);
	if (size != read_urandom(fd, &g_crypt->xold, size)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL:  read('%s',...,%d) failed (%s)\n",
			"/dev/urandom", (unsigned)size, strerror(errno)));
		errno = EIO;
		return -1;
	}
	size = sizeof(g_crypt->yold);
	if (size != read_urandom(fd, &g_crypt->yold, size)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL:  read('%s',...,%d) failed (%s)\n",
			"/dev/urandom", (unsigned)size, strerror(errno)));
		errno = EIO;
		return -1;
	}
	size = sizeof(g_crypt->zold);
	if (size != read_urandom(fd, &g_crypt->zold, size)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL:  read('%s',...,%d) failed (%s)\n",
			"/dev/urandom", (unsigned)size, strerror(errno)));
		errno = EIO;
		return -1;
	}
	size = sizeof(g_crypt->offs);
	if (size != read_urandom(fd, &g_crypt->offs, size)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL:  read('%s',...,%d) failed (%s)\n",
			"/dev/urandom", (unsigned)size, strerror(errno)));
		errno = EIO;
		return -1;
	}
	size = sizeof(g_crypt->key);
	if (size != read_urandom(fd, g_crypt->key, size)) {
		LOGS(L_CRYPTO,L_ERROR,("FATAL:  read('%s',...,%d) failed (%s)\n",
			"/dev/urandom", (unsigned)size, strerror(errno)));
		errno = EIO;
		return -1;
	}
	close(fd);
	fd = -1;

	g_crypt->xold &= FXMASK;
	g_crypt->yold &= FXMASK;
	g_crypt->zold &= FXMASK;
	g_crypt->offs %= KEYSIZE;

	/* check 100000 32bit words */
	size = 100000 * sizeof(uint32_t);
	buffer = (uint8_t *)xcalloc(size, sizeof(uint8_t));
	rnd_get(buffer, size);
	crypto_diequick("PRNG", buffer, size);
	xfree(buffer);

	/* initialize the crypto methods */
	if (0 != (rc = crypt0_crypto())) {
		LOGS(L_CRYPTO,L_ERROR,("crypto module '%s' failed (%s)\n",
			module_name[MODULE_CRYPT0], strerror(errno)));
		return -1;
	}

	if (0 != (rc = crypt7_crypto())) {
		LOGS(L_CRYPTO,L_ERROR,("crypto module '%s' failed (%s)\n",
			module_name[MODULE_TWOFISH], strerror(errno)));
		return -1;
	}

	LOGS(L_CRYPTO,L_MINOR,("crypto modules initialized\n"));

	return rc;
}

#if	CRYPT_TEST
configuration_t *g_conf = NULL;
#define	SIZE	1024

/* dummies for the IPC semaphores (not really needed in the crypt test) */
int osd_sem_init(osd_sem_t *sem, int p, int v SEM_FILELINEARGS)
{
	(void)sem;
	(void)p;
	(void)v;
	return 0;
}

int osd_sem_wait(osd_sem_t *sem SEM_FILELINEARGS)
{
	(void)sem;
	return 0;
}

int osd_sem_post(osd_sem_t *sem SEM_FILELINEARGS)
{
	(void)sem;
	return 0;
}

int info(const char *fmt, ...)
{
	va_list ap;
	int len;

	va_start(ap, fmt);
	len = vfprintf(stdout, fmt, ap);
	va_end(ap);
	return len;
}

int die(int rc, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	exit(rc);
}

#include "key.h"

int main(int argc, char **argv)
{
	char rndname[MAXPATHLEN] = "/dev/urandom";
	char outname[MAXPATHLEN] = "crypt.bin";
	FILE *out;
	size_t size = SIZE;
	size_t count = 1;
	size_t n;
	unsigned char *prng;
	uint8_t key[16];
	dragon_t *ctx;
	int i;
	int rc;

	for (i = 1; i < argc; i++) {
		if (0 == strcmp(argv[i], "--diehard")) {
			size = 0x1000;
			count = 0x1000;
		} else if (0 == strncmp(argv[i], "-r", 2) && strlen(argv[i]) > 2) {
			strcpy(rndname, &argv[i][2]);
		} else if (0 == strcmp(argv[i], "-r")) {
			strcpy(rndname, argv[++i]);
		} else if (0 == strncmp(argv[i], "-o", 2) && strlen(argv[i]) > 2) {
			strcpy(rndname, &argv[i][2]);
		} else if (0 == strcmp(argv[i], "-o")) {
			strcpy(rndname, argv[++i]);
		} else if (0 == strncmp(argv[i], "-s", 2) && strlen(argv[i]) > 2) {
			size = strtoul(&argv[i][2], NULL, 0);
		} else if (0 == strcmp(argv[i], "-s")) {
			size = strtoul(argv[++i], NULL, 0);
		} else if (0 == strncmp(argv[i], "-c", 2) && strlen(argv[i]) > 2) {
			count = strtoul(&argv[i][2], NULL, 0);
		} else if (0 == strcmp(argv[i], "-c")) {
			count = strtoul(argv[++i], NULL, 0);
		}
	}

	g_conf = (configuration_t *)xcalloc(1, sizeof(configuration_t));
	strcpy(g_conf->logfile, "crypt.log");
	for (n = 0; n < LOGSECTIONS; n++)
		g_conf->loglevel[n] = L_DEBUGX;

	if (0 != crypto()) {
		fprintf(stderr, "crypto() call failed\n");
		exit(1);
	}

	printf("create output file: %s\n", outname);
	out = fopen(outname, "wb");
	if (NULL == out) {
		perror(outname);
		exit(3);
	}

	prng = scalloc(size, sizeof(uint8_t));

	for (n = 0; n < count; n++) {
		if (0 != (rc = rnd_get(prng, size))) {
			fprintf(stderr, "rnd_get() failed (%d)", rc);
			exit(4);
		}
		if (size != fwrite(prng, 1, size, out)) {
			perror("fwrite");
			exit(5);
		}
	}

	fclose(out);

	out = fopen("key.bin", "wb");
	if (NULL == out) {
		perror("key,bin");
		exit(5);
	}
	if (0 != (rc = rnd_get(key, sizeof(key)))) {
		fprintf(stderr, "rnd_get() failed (%d)", rc);
		exit(6);
	}
	ctx = key_setup(key);

	for (n = 0; n < count; n++) {
		if (0 != (rc = key_output(ctx, prng, size))) {
			fprintf(stderr, "key_output() failed (%d)", rc);
			exit(4);
		}
		if (size != fwrite(prng, 1, size, out)) {
			perror("fwrite");
			exit(5);
		}
	}
	fclose(out);
	key_destroy(ctx);

	return 0;
}

#endif
